﻿using System;
using System.Diagnostics;

namespace Qiang.Topology.Geometries
{    
    /// <summary>
    /// The types of Precision Model which NTS supports.
    /// </summary>
    public enum PrecisionModels : int
    {
        /// <summary> 
        /// Floating precision corresponds to the standard 
        /// double-precision floating-point representation, which is
        /// based on the IEEE-754 standard
        /// </summary>
        Floating = 0,

        /// <summary>
        /// Floating single precision corresponds to the standard
        /// single-precision floating-point representation, which is
        /// based on the IEEE-754 standard
        /// </summary>
        FloatingSingle = 1,

        /// <summary> 
        /// Fixed Precision indicates that coordinates have a fixed number of decimal places.
        /// The number of decimal places is determined by the log10 of the scale factor.
        /// </summary>
        Fixed = 2,
    }


    /// <summary> 
    /// Specifies the precision model of the <c>Coordinate</c>s in a <c>Geometry</c>.
    /// In other words, specifies the grid of allowable
    /// points for all <c>Geometry</c>s.
    /// 
    /// The {makePrecise} method allows rounding a coordinate to
    /// a "precise" value; that is, one whose
    /// precision is known exactly.
    /// 
    /// Coordinates are assumed to be precise in geometries.
    /// That is, the coordinates are assumed to be rounded to the
    /// precision model given for the point.
    /// NTS input routines automatically round coordinates to the precision model
    /// before creating Geometries.
    /// All internal operations
    /// assume that coordinates are rounded to the precision model.
    /// Constructive methods (such as boolean operations) always round computed
    /// coordinates to the appropriate precision model.
    /// 
    /// Currently three types of precision model are supported:
    /// 
    /// FLOATING - represents full double precision floating point.
    /// This is the default precision model used in NTS
    /// FLOATING_SINGLE - represents single precision floating point.
    /// FIXED - represents a model with a fixed number of decimal places.
    /// A Fixed Precision Model is specified by a scale factor.
    /// The scale factor specifies the grid which numbers are rounded to.
    /// Input coordinates are mapped to fixed coordinates according to the following
    /// equations:
    /// 
    ///  jtsPt.x = round( (inputPt.x * scale ) / scale
    ///  jtsPt.y = round( (inputPt.y * scale ) / scale
    /// 
    /// 
    /// Coordinates are represented internally as double-precision values.
    /// Since .NET uses the IEEE-394 floating point standard, this
    /// provides 53 bits of precision. (Thus the maximum precisely representable
    /// integer is 9,007,199,254,740,992).
    /// 
    /// NTS methods currently do not handle inputs with different precision models.
    /// </summary>
    public class PrecisionModel :IComparable
    {
        /// <summary>  
        /// The maximum precise value representable in a double. Since IEE754
        /// double-precision numbers allow 53 bits of mantissa, the value is equal to
        /// 2^53 - 1.  This provides <i>almost</i> 16 decimal digits of precision.
        /// </summary>
        public const double MaximumPreciseValue = 9007199254740992.0;

        /// <summary>
        /// The type of PrecisionModel this represents.
        /// </summary>
        private PrecisionModels modelType;

        /// <summary> 
        /// The scale factor which determines the number of decimal places in fixed precision.
        /// </summary>
        private double scale;

        /// <summary> 
        /// Creates a <c>PrecisionModel</c> with a default precision
        /// of FLOATING.
        /// </summary>
        public PrecisionModel() 
        {
            // default is floating precision
            modelType = PrecisionModels.Floating;
        }

        /// <summary>
        /// Creates a <c>PrecisionModel</c> that specifies
        /// an explicit precision model type.
        /// If the model type is FIXED the scale factor will default to 1.
        /// </summary>
        /// <param name="modelType">
        /// The type of the precision model.
        /// </param>
        public PrecisionModel(PrecisionModels modelType)
        {
            this.modelType = modelType;

            if (modelType == PrecisionModels.Fixed)
                Scale = 1.0;            
        }

        /// <summary>  
        /// Creates a <c>PrecisionModel</c> that specifies Fixed precision.
        /// Fixed-precision coordinates are represented as precise internal coordinates,
        /// which are rounded to the grid defined by the scale factor.        
        /// </summary>
        /// <param name="scale">
        /// Amount by which to multiply a coordinate after subtracting
        /// the offset, to obtain a precise coordinate
        /// </param>
        /// <param name="offsetX"> not used.</param>
        /// <param name="offsetY"> not used.</param>
        /// <deprecated>
        /// Offsets are no longer supported, since internal representation is rounded floating point.
        /// </deprecated>
        [Obsolete("Offsets are no longer supported, since internal representation is rounded floating point")]
        public PrecisionModel(double scale, double offsetX, double offsetY) 
        {
            modelType = PrecisionModels.Fixed;
            Scale = scale;
        }

        /// <summary>  
        /// Creates a <c>PrecisionModel</c> that specifies Fixed precision.
        /// Fixed-precision coordinates are represented as precise internal coordinates,
        /// which are rounded to the grid defined by the scale factor.
        /// </summary>
        /// <param name="scale">
        /// Amount by which to multiply a coordinate after subtracting
        /// the offset, to obtain a precise coordinate.
        /// </param>  
        public PrecisionModel(double scale) 
        {
            modelType = PrecisionModels.Fixed;
            Scale = scale;
        }

        /// <summary> 
        /// Copy constructor to create a new <c>PrecisionModel</c>
        /// from an existing one.
        /// </summary>
        public PrecisionModel(PrecisionModel pm) 
        {
            modelType = pm.modelType;
            scale = pm.scale;
        }

        /// <summary>
        /// Return HashCode.
        /// </summary>
        public override int GetHashCode()
        {
            int result = 17;
            result = 37 * result + (int)scale;
            return result;
        }


        /// <summary> 
        /// Tests whether the precision model supports floating point.
        /// </summary>
        /// <returns> 
        /// <c>true</c> if the precision model supports floating point.
        /// </returns>
        public virtual bool IsFloating
        {
            get
            {
                return modelType == PrecisionModels.Floating || modelType == PrecisionModels.FloatingSingle;
            }
        }

        /// <summary>
        /// Returns the maximum number of significant digits provided by this
        /// precision model.
        /// Intended for use by routines which need to print out precise values.
        /// </summary>
        /// <returns>
        /// The maximum number of decimal places provided by this precision model.
        /// </returns>
        public virtual int MaximumSignificantDigits
        {
            get
            {
                int maxSigDigits = 16;
                if (modelType == PrecisionModels.Floating)
                    maxSigDigits = 16;                
                else if (modelType == PrecisionModels.FloatingSingle)
                    maxSigDigits = 6;
                else if (modelType == PrecisionModels.Fixed)
                    maxSigDigits = 1 + (int)Math.Ceiling(Math.Log(Scale) / Math.Log(10));
                return maxSigDigits;
            }
	    }

        /// <summary>
        /// Returns the multiplying factor used to obtain a precise coordinate.
        /// This method is private because PrecisionModel is intended to
        /// be an immutable (value) type.
        /// </summary>
        /// <returns>    
        /// the amount by which to multiply a coordinate after subtracting
        /// the offset.
        /// </returns>
        public virtual double Scale
        {
            get
            {
                return scale;
            }
            set
            {
                this.scale = Math.Abs(value);
            }
        }

        /// <summary> 
        /// Gets the type of this PrecisionModel.
        /// </summary>
        /// <returns>The type of this PrecisionModel.</returns>
        public virtual PrecisionModels GetPrecisionModelType()
        {                        
            return modelType;
        }

        /// <summary> 
        /// Returns the x-offset used to obtain a precise coordinate.
        /// </summary>
        /// <returns>
        /// The amount by which to subtract the x-coordinate before
        /// multiplying by the scale.
        /// </returns>
        /// <deprecated> 
        /// Offsets are no longer used.
        /// </deprecated>
        [Obsolete("Offsets are no longer used")]
        public virtual double OffsetX
        {
            get
            {
                //We actually don't use offsetX and offsetY anymore ... [Jon Aquino]
                return 0;
            }
        }

        /// <summary> 
        /// Returns the y-offset used to obtain a precise coordinate.
        /// </summary>
        /// <returns> 
        /// The amount by which to subtract the y-coordinate before
        /// multiplying by the scale
        /// </returns>
        /// <deprecated> 
        /// Offsets are no longer used.
        /// </deprecated>
        [Obsolete("Offsets are no longer used")]
        public virtual double OffsetY
        {
            get
            {
                return 0;
            }
        }

        /// <summary>  
        /// Sets <c>internal</c> to the precise representation of <c>external</c>.
        /// </summary>
        /// <param name="external">
        /// The original coordinate.
        /// </param>
        /// <param name="internal">
        /// The coordinate whose values will be changed to the
        /// precise representation of <c>external</c>.
        /// </param>
        /// <deprecated> 
        /// Use MakePrecise instead.
        /// </deprecated>
        [Obsolete("Use MakePrecise instead")]
        public virtual void ToInternal(Coordinate cexternal, Coordinate cinternal) 
        {
            if (IsFloating) 
            {
                cinternal.X = cexternal.X;
                cinternal.Y = cexternal.Y;
            }
            else 
            {
                cinternal.X = MakePrecise(cexternal.X);
                cinternal.Y = MakePrecise(cexternal.Y);
            }
            cinternal.Z = cexternal.Z;
        }

        /// <summary>  
        /// Returns the precise representation of <c>external</c>.
        /// </summary>
        /// <param name="external">
        /// The original coordinate
        /// </param>
        /// <returns>
        /// The coordinate whose values will be changed to the precise
        /// representation of <c>external</c>
        /// </returns>
        /// <deprecated>
        /// Use MakePrecise instead.
        /// </deprecated>
        [Obsolete("Use MakePrecise instead")]
        public virtual Coordinate ToInternal(Coordinate cexternal) 
        {
            Coordinate cinternal = new Coordinate(cexternal);
            MakePrecise(cinternal);
            return cinternal;
        }

        /// <summary>
        /// Returns the external representation of <c>internal</c>.
        /// </summary>
        /// <param name="internal">
        /// The original coordinate.
        /// </param>
        /// <returns>
        /// The coordinate whose values will be changed to the
        /// external representation of <c>internal</c>.
        /// </returns>
        /// <deprecated>
        /// No longer needed, since internal representation is same as external representation.
        /// </deprecated>
        [Obsolete("No longer needed, since internal representation is same as external representation")]
        public virtual Coordinate ToExternal(Coordinate cinternal) 
        {
            Coordinate cexternal = new Coordinate(cinternal);
            return cexternal;
        }

        /// <summary>  
        /// Sets <c>external</c> to the external representation of <c>internal</c>.
        /// </summary>
        /// <param name="internal">
        /// The original coordinate.
        /// </param>
        /// <param name="external">
        /// The coordinate whose values will be changed to the
        /// external representation of <c>internal</c>.
        /// </param>
        /// <deprecated>
        /// No longer needed, since internal representation is same as external representation.
        /// </deprecated>
        [Obsolete("No longer needed, since internal representation is same as external representation")]
        public virtual void ToExternal(Coordinate cinternal, Coordinate cexternal) 
        {
            cexternal.X = cinternal.X;
            cexternal.Y = cinternal.Y;
        }

        /// <summary> 
        /// Rounds a numeric value to the PrecisionModel grid.
        /// </summary>
        public virtual double MakePrecise(double val) 
        {
  	        if (modelType == PrecisionModels.FloatingSingle)
            {
  		        float floatSingleVal = (float) val;
  		        return (double) floatSingleVal;
  	        }
  	        if (modelType == PrecisionModels.Fixed) 
  		        return Math.Round(val * scale) / scale;  	        
  	        // modelType == FLOATING - no rounding necessary
  	        return val;
        }

        /// <summary> 
        /// Rounds a Coordinate to the PrecisionModel grid.
        /// </summary>
        public virtual void MakePrecise(Coordinate coord)
        {
            // optimization for full precision
            if (modelType == PrecisionModels.Floating) return;

            coord.X = MakePrecise(coord.X);
            coord.Y = MakePrecise(coord.Y);
            //MD says it's OK that we're not makePrecise'ing the z [Jon Aquino]
        }

        public override string ToString() 
        {
  	        string description = "UNKNOWN";
  	        if (modelType == PrecisionModels.Floating)
  		        description = "Floating";  	        
            else if (modelType == PrecisionModels.FloatingSingle)
  		        description = "Floating-Single";  	        
            else if (modelType == PrecisionModels.Fixed) 
  		        description = "Fixed (Scale=" + Scale + ")";  	        
  	        return description;
        }

        public override bool Equals(object other) 
        {
            if (! (other is PrecisionModel))
                return false;            
            PrecisionModel otherPrecisionModel = (PrecisionModel) other;
            return modelType == otherPrecisionModel.modelType && scale == otherPrecisionModel.scale;
        }

        /// <summary> 
        /// Compares this {PrecisionModel} object with the specified object for order.
        /// A PrecisionModel is greater than another if it provides greater precision.
        /// The comparison is based on the value returned by the
        /// {getMaximumSignificantDigits) method.
        /// This comparison is not strictly accurate when comparing floating precision models
        /// to fixed models; however, it is correct when both models are either floating or fixed.
        /// </summary>
        /// <param name="o">
        /// The <c>PrecisionModel</c> with which this <c>PrecisionModel</c>
        /// is being compared.
        /// </param>
        /// <returns>
        /// A negative integer, zero, or a positive integer as this <c>PrecisionModel</c>
        /// is less than, equal to, or greater than the specified <c>PrecisionModel</c>.
        /// </returns>
        public virtual int CompareTo(object o) 
        {
            PrecisionModel other = (PrecisionModel) o;

            int sigDigits = MaximumSignificantDigits;
            int otherSigDigits = other.MaximumSignificantDigits;
            return (sigDigits).CompareTo(otherSigDigits);        
        }
    }
}
